﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Text.RegularExpressions;

namespace Genuilder.Extensions.XamlResource
{
	public class XamlType
	{
		public string Name
		{
			get;
			set;
		}
		public XamlNamespace Namespace
		{
			get;
			set;
		}
		public override string ToString()
		{
			return Name + " {" + Namespace.ToString() + "}";
		}
	}
	public class XamlNamespace
	{
		public static XamlNamespace Parse(string ns)
		{
			if(Uri.IsWellFormedUriString(ns, UriKind.Absolute))
			{
				var uri = new Uri(ns);
				if(uri.Host == "")
				{
					return XamlCLRNamespace.Parse(ns);
				}
				return new XamlURINamespace()
				{
					Uri = uri
				};
			}
			return XamlCLRNamespace.Parse(ns);
		}
	}

	public class XamlURINamespace : XamlNamespace
	{
		public Uri Uri
		{
			get;
			set;
		}
		public override string ToString()
		{
			return Uri.ToString();
		}
	}
	public class XamlCLRNamespace : XamlNamespace
	{
		public new static XamlCLRNamespace Parse(string ns)
		{
			var match = Regex.Match(ns, "clr-namespace:([^;]+)(;assembly=(.*))?");
			if(match == null)
				throw new FormatException(ns + " is not a valid Xaml CLR namespace");
			var clrNs = new XamlCLRNamespace()
			{
				Namespace = match.Groups[1].Value
			};
			if(match.Groups.Count > 2)
				clrNs.Assembly = match.Groups[3].Value;
			return clrNs;
		}
		public String Namespace
		{
			get;
			set;
		}
		public String Assembly
		{
			get;
			set;
		}
		public override string ToString()
		{
			if(String.IsNullOrEmpty(Assembly))
			{
				return String.Format("clr-namespace:{0}", Namespace);
			}
			return String.Format("clr-namespace:{0};assembly={1}", Namespace, Assembly);
		}
	}

	public class XamlResource
	{
		public String Key
		{
			get;
			set;
		}
		public XamlType Type
		{
			get;
			set;
		}
	}

	public class TypeName
	{
		public string Name
		{
			get;
			set;
		}
		public String Namespace
		{
			get;
			set;
		}
		public override string ToString()
		{
			if(String.IsNullOrEmpty(Namespace))
				return Name;
			return String.Join(".", Namespace, Name);
		}

		public static TypeName Parse(string fullName)
		{
			var names = fullName.Split('.');
			if(names.Length == 1)
				return new TypeName()
				{
					Name = names[0],
					Namespace = ""
				};
			return new TypeName()
			{
				Name = names[names.Length - 1],
				Namespace = String.Join(".", names.Take(names.Length - 1).ToArray())
			};
		}
	}

	public class XamlResourceReader
	{
		private XmlReader xmlReader;

		public XamlResourceReader(string filePath)
			: this(XmlReader.Create(filePath))
		{
		}

		public XamlResourceReader(XmlReader reader)
		{
			this.xmlReader = reader;
		}



		private TypeName _ResourceHolder;
		public TypeName ResourceHolder
		{
			get
			{
				EnsureResourceHolder();
				return _ResourceHolder;
			}
		}

		bool _ResourceHolderInitialized = false;
		string _ResourceElementName;
		private void EnsureResourceHolder()
		{
			if(!_ResourceHolderInitialized)
			{
				_ResourceHolderInitialized = true;
				MoveWhile(xmlReader, IsNotElement);
				_ResourceElementName = xmlReader.LocalName + ".Resources";
				xmlReader.MoveToAttribute("Class", "http://schemas.microsoft.com/winfx/2006/xaml");
				if(String.IsNullOrEmpty(xmlReader.Value))
					return;
				_ResourceHolder = TypeName.Parse(xmlReader.Value);

				while(MoveWhile(xmlReader, IsNotElement))
				{
					if(xmlReader.Depth == 1 && _ResourceElementName == xmlReader.LocalName)
					{
						break;
					}
					if(xmlReader.Depth == 1 && xmlReader.LocalName != _ResourceElementName)
						xmlReader.Skip();
				}
			}
		}

		public XamlResource Read()
		{
			EnsureResourceHolder();
			if(_ResourceHolder == null)
				return null;

			do
			{
				if(xmlReader.EOF || IsEndResource)
					return null;
				xmlReader.Read();
			}
			while(IsNotElement(xmlReader));

			var xamlType = new XamlType()
				{
					Name = xmlReader.LocalName,
					Namespace = XamlNamespace.Parse(xmlReader.NamespaceURI)
				};
			if(!xmlReader.MoveToAttribute("Key", "http://schemas.microsoft.com/winfx/2006/xaml"))
				return Read();
			var result = new XamlResource()
			{
				Key = xmlReader.Value,
				Type = xamlType
			};
			xmlReader.Skip();
			return result;
		}

		private bool IsEndResource
		{
			get
			{
				return xmlReader.NodeType == XmlNodeType.EndElement && xmlReader.LocalName == _ResourceElementName && xmlReader.Depth == 1;
			}
		}

		public IEnumerable<XamlResource> ReadAll()
		{
			XamlResource key = null;
			while((key = Read()) != null)
				yield return key;
		}
		private bool IsNotElement(XmlReader xmlReader)
		{
			return xmlReader.NodeType != XmlNodeType.Element;
		}


		private bool MoveWhile(XmlReader xmlReader, Func<XmlReader, bool> predicate)
		{
			do
			{
				if(xmlReader.EOF)
					return false;
				xmlReader.Read();
			}
			while(predicate(xmlReader));
			return true;
		}
	}
}
